/**
 * 
 */
package truerefactor.codesmell;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.MethodNode;
import truerefactor.metrics.CBO;
import truerefactor.metrics.RFC;
import truerefactor.refactor.Refactoring;
import truerefactor.refactor.RefactoringFactory;

/**
 * @author Isaac
 */
public class ShotgunSurgery extends CodeSmell {

    /**
     * 
     */
    public ShotgunSurgery()
    {
        this.shortName = "SHOSUR";
        this.name = "Shotgun Surgery";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.codesmell.CodeSmell#measure(truerefactor.graph.CodeGraph,
     * boolean, java.util.List)
     */
    @Override
    protected double measure(CodeGraph graph, boolean initial, List<List<Refactoring>> sequences)
    {
        List<ClassNode> classes = graph.getClasses();

        double smell = 0;

        for (ClassNode cNode : classes)
        {
            double iCBO = CBO.measure(cNode, graph);
            double iRFC = RFC.measure(cNode);

            if (iCBO >= 4 && iRFC >= 4)
            {
                if (initial)
                {
                    List<Refactoring> refactoring = new ArrayList<Refactoring>();
                    Set<ClassNode> foundClasses = new HashSet<ClassNode>();
                    decide(cNode, foundClasses);
                    generateRefactorings(cNode, foundClasses, refactoring);
                }
                smell++;
            }
        }

        return smell;
    }

    private void generateRefactorings(ClassNode cNode, Set<ClassNode> foundClasses, List<Refactoring> refactoring)
    {
        if (foundClasses.size() > 1)
        {
            for (ClassNode found : foundClasses)
            {
                for (CodeNode method : cNode.getMethods())
                {
                    if (method instanceof MethodNode)
                    {
                        for (MethodNode mNode : ((MethodNode) method).getAssociatedMethods())
                        {
                            if (mNode.getParentClass().equals(found))
                            {
                                CodeNode operands[] = { mNode, found };
                                refactoring.add(RefactoringFactory.getInstance().createMoveMethod(operands));
                            }
                        }
                    }
                }

                for (CodeNode attr : cNode.getAttributes())
                {
                    if (attr instanceof AttributeNode)
                    {
                        for (MethodNode mNode : ((AttributeNode) attr).getAssociatedMethods())
                        {
                            if (mNode.getParentClass().equals(found))
                            {
                                CodeNode operands[] = { attr, found };
                                refactoring.add(RefactoringFactory.getInstance().createMoveField(operands));
                            }
                        }
                    }
                }
            }
        }
        else
        {
            ClassNode source = cNode;
            ClassNode target = null;
            for (ClassNode node : foundClasses)
                target = node;

            if (target != null)
            {
                CodeNode operands[] = { source, target };
                refactoring.add(RefactoringFactory.getInstance().createInlineClass(operands));
            }
        }
    }

    private void decide(ClassNode cNode, Set<ClassNode> foundClasses)
    {
        List<CodeNode> attrs = cNode.getAttributes();
        List<CodeNode> methods = cNode.getMethods();

        for (CodeNode attr : attrs)
        {
            if (attr instanceof AttributeNode)
            {
                for (MethodNode assoc : ((AttributeNode) attr).getAssociatedMethods())
                {
                    foundClasses.add(assoc.getParentClass());
                }
            }
        }

        for (CodeNode method : methods)
        {
            if (method instanceof MethodNode)
            {
                for (MethodNode mNode : ((MethodNode) method).getAssociatedMethods())
                {
                    foundClasses.add(mNode.getParentClass());
                }
            }
        }

        Set<ClassNode> toRemove = new HashSet<ClassNode>();
        for (ClassNode clNode : foundClasses)
        {
            for (ClassNode parent : clNode.getParentClasses())
            {
                if (foundClasses.contains(parent))
                {
                    toRemove.add(clNode);
                    break;
                }
            }
        }

        foundClasses.removeAll(toRemove);
    }
}
